winsafe\gui\native_controls/
edit.rs

1use std::any::Any;
2use std::marker::PhantomPinned;
3use std::pin::Pin;
4use std::sync::Arc;
5
6use crate::co;
7use crate::decl::*;
8use crate::gui::{events::*, privs::*, *};
9use crate::msg::*;
10use crate::prelude::*;
11
12struct EditObj {
13	base: BaseCtrl,
14	events: EditEvents,
15	_pin: PhantomPinned,
16}
17
18native_ctrl! { Edit: EditObj => EditEvents;
19	/// Native
20	/// [edit](https://learn.microsoft.com/en-us/windows/win32/controls/about-edit-controls)
21	/// (text box) control.
22}
23
24impl Edit {
25	/// Instantiates a new `Edit` object, to be created on the parent window
26	/// with
27	/// [`HWND::CreateWindowEx`](crate::prelude::user_Hwnd::CreateWindowEx).
28	///
29	/// # Panics
30	///
31	/// Panics if the parent window was already created – that is, you cannot
32	/// dynamically create an `Edit` in an event closure.
33	///
34	/// # Examples
35	///
36	/// ```no_run
37	/// use winsafe::{self as w, prelude::*, gui};
38	///
39	/// let wnd: gui::WindowMain; // initialized somewhere
40	/// # let wnd = gui::WindowMain::new(gui::WindowMainOpts::default());
41	///
42	/// let txt = gui::Edit::new(
43	///     &wnd,
44	///     gui::EditOpts {
45	///         position: gui::dpi(10, 10),
46	///         width: gui::dpi_x(120),
47	///         ..Default::default()
48	///     },
49	/// );
50	/// ```
51	#[must_use]
52	pub fn new(parent: &(impl GuiParent + 'static), opts: EditOpts) -> Self {
53		let ctrl_id = auto_id::set_if_zero(opts.ctrl_id);
54		let new_self = Self(Arc::pin(EditObj {
55			base: BaseCtrl::new(ctrl_id),
56			events: EditEvents::new(parent, ctrl_id),
57			_pin: PhantomPinned,
58		}));
59
60		let self2 = new_self.clone();
61		let parent2 = parent.clone();
62		parent
63			.as_ref()
64			.before_on()
65			.wm(parent.as_ref().wnd_ty().creation_msg(), move |_| {
66				self2.0.base.create_window(
67					opts.window_ex_style,
68					"EDIT",
69					Some(&opts.text),
70					opts.window_style | opts.control_style.into(),
71					opts.position.into(),
72					SIZE::new(opts.width, opts.height),
73					&parent2,
74				)?;
75				ui_font::set(self2.hwnd())?;
76				parent2
77					.as_ref()
78					.add_to_layout(self2.hwnd(), opts.resize_behavior)?;
79				Ok(0) // ignored
80			});
81
82		new_self.default_message_handlers(parent);
83		new_self
84	}
85
86	/// Instantiates a new `Edit` object, to be loaded from a dialog resource
87	/// with [`HWND::GetDlgItem`](crate::prelude::user_Hwnd::GetDlgItem).
88	///
89	/// # Panics
90	///
91	/// Panics if the parent dialog was already created – that is, you cannot
92	/// dynamically create an `Edit` in an event closure.
93	#[must_use]
94	pub fn new_dlg(
95		parent: &(impl GuiParent + 'static),
96		ctrl_id: u16,
97		resize_behavior: (Horz, Vert),
98	) -> Self {
99		let new_self = Self(Arc::pin(EditObj {
100			base: BaseCtrl::new(ctrl_id),
101			events: EditEvents::new(parent, ctrl_id),
102			_pin: PhantomPinned,
103		}));
104
105		let self2 = new_self.clone();
106		let parent2 = parent.clone();
107		parent.as_ref().before_on().wm_init_dialog(move |_| {
108			self2.0.base.assign_dlg(&parent2)?;
109			parent2
110				.as_ref()
111				.add_to_layout(self2.hwnd(), resize_behavior)?;
112			Ok(true) // ignored
113		});
114
115		new_self.default_message_handlers(parent);
116		new_self
117	}
118
119	fn default_message_handlers(&self, parent: &(impl GuiParent + 'static)) {
120		let self2 = self.clone();
121		let parent2 = parent.clone();
122		parent
123			.as_ref()
124			.before_on()
125			.wm_command(self.ctrl_id(), co::EN::CHANGE, move || {
126				// EN_CHANGE is first sent to the control before CreateWindowEx()
127				// returns, so if the user handles EN_CHANGE, the Edit HWND won't be
128				// set yet. So we set the HWND here.
129				if *self2.hwnd() == HWND::NULL {
130					let hctrl = parent2.as_ref().hwnd().GetDlgItem(self2.ctrl_id())?;
131					self2.0.base.set_hwnd(hctrl);
132				}
133				Ok(())
134			});
135	}
136
137	/// Hides any balloon tip by sending an
138	/// [`em::HideBalloonTip`](crate::msg::em::HideBalloonTip) message.
139	pub fn hide_balloon_tip(&self) -> SysResult<()> {
140		unsafe { self.hwnd().SendMessage(em::HideBalloonTip {}) }
141	}
142
143	/// Limits the number of characters that can be type by sending an
144	/// [`em::SetLimitText`](crate::msg::em::SetLimitText) message.
145	pub fn limit_text(&self, max_chars: Option<u32>) {
146		unsafe {
147			self.hwnd().SendMessage(em::SetLimitText { max_chars });
148		}
149	}
150
151	/// Sets the selection range of the text by sending an
152	/// [`em::SetSel`](crate::msg::em::SetSel) message.
153	///
154	/// # Examples
155	///
156	/// Selecting all text in the control:
157	///
158	/// ```no_run
159	/// use winsafe::{self as w, prelude::*, gui};
160	///
161	/// let my_edit: gui::Edit; // initialized somewhere
162	/// # let wnd = gui::WindowMain::new(gui::WindowMainOpts::default());
163	/// # let my_edit = gui::Edit::new(&wnd, gui::EditOpts::default());
164	///
165	/// my_edit.set_selection(0, -1);
166	/// ```
167	///
168	/// Clearing the selection:
169	///
170	/// ```no_run
171	/// use winsafe::gui;
172	///
173	/// let my_edit: gui::Edit; // initialized somewhere
174	/// # let wnd = gui::WindowMain::new(gui::WindowMainOpts::default());
175	/// # let my_edit = gui::Edit::new(&wnd, gui::EditOpts::default());
176	///
177	/// my_edit.set_selection(-1, -1);
178	/// ```
179	pub fn set_selection(&self, start: i32, end: i32) {
180		unsafe {
181			self.hwnd().SendMessage(em::SetSel { start, end });
182		}
183	}
184
185	/// Sets the text by calling
186	/// [`HWND::SetWindowText`](crate::prelude::user_Hwnd::SetWindowText).
187	pub fn set_text(&self, text: &str) -> SysResult<()> {
188		self.hwnd().SetWindowText(text)?;
189		Ok(())
190	}
191
192	/// Displays a balloon tip by sending an
193	/// [`em::ShowBalloonTip`](crate::msg::em::ShowBalloonTip) message.
194	pub fn show_ballon_tip(&self, title: &str, text: &str, icon: co::TTI) -> SysResult<()> {
195		let mut title16 = WString::from_str(title);
196		let mut text16 = WString::from_str(text);
197
198		let mut info = EDITBALLOONTIP::default();
199		info.set_pszTitle(Some(&mut title16));
200		info.set_pszText(Some(&mut text16));
201		info.ttiIcon = icon;
202
203		unsafe { self.hwnd().SendMessage(em::ShowBalloonTip { info: &info }) }
204	}
205
206	/// Retrieves the text by calling
207	/// [`HWND::GetWindowText`](crate::prelude::user_Hwnd::GetWindowText).
208	#[must_use]
209	pub fn text(&self) -> SysResult<String> {
210		self.hwnd().GetWindowText()
211	}
212}
213
214/// Options to create an [`Edit`](crate::gui::Edit) programmatically with
215/// [`Edit::new`](crate::gui::Edit::new).
216pub struct EditOpts {
217	/// Text of the control to be
218	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
219	///
220	/// Defaults to empty string.
221	pub text: String,
222	/// Left and top position coordinates of control within parent's client
223	/// area, to be
224	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
225	///
226	/// Defaults to `gui::dpi(0, 0)`.
227	pub position: (i32, i32),
228	/// Control width to be
229	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
230	///
231	/// Defaults to `gui::dpi_x(100)`.
232	pub width: i32,
233	/// Control height to be
234	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
235	///
236	/// Defaults to `gui::dpi_y(23)`.
237	///
238	/// **Note:** You should change the default height only in a multi-line
239	/// edit, otherwise it will look off.
240	pub height: i32,
241	/// Edit styles to be
242	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
243	///
244	/// Defaults to `ES::AUTOHSCROLL | ES::NOHIDESEL`.
245	///
246	/// Suggestions:
247	/// * add `ES::PASSWORD` for a password input;
248	/// * add `ES::NUMBER` to accept only numbers;
249	/// * replace with `ES::MULTILINE | ES::WANTRETURN | ES::AUTOVSCROLL | ES::NOHIDESEL` for a multi-line edit.
250	pub control_style: co::ES,
251	/// Window styles to be
252	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
253	///
254	/// Defaults to `WS::CHILD | WS::GROUP | WS::TABSTOP | WS::VISIBLE`.
255	pub window_style: co::WS,
256	/// Extended window styles to be
257	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
258	///
259	/// Defaults to `WS_EX::LEFT | WS_EX::CLIENTEDGE`.
260	pub window_ex_style: co::WS_EX,
261
262	/// The control ID.
263	///
264	/// Defaults to an auto-generated ID.
265	pub ctrl_id: u16,
266	/// Horizontal and vertical behavior of the control when the parent window
267	/// is resized.
268	///
269	/// **Note:** You should use `Vert::Resize` only in a multi-line edit.
270	///
271	/// Defaults to `(gui::Horz::None, gui::Vert::None)`.
272	pub resize_behavior: (Horz, Vert),
273}
274
275impl Default for EditOpts {
276	fn default() -> Self {
277		Self {
278			text: "".to_owned(),
279			position: dpi(0, 0),
280			width: dpi_x(100),
281			height: dpi_y(23),
282			control_style: co::ES::AUTOHSCROLL | co::ES::NOHIDESEL,
283			window_style: co::WS::CHILD | co::WS::GROUP | co::WS::TABSTOP | co::WS::VISIBLE,
284			window_ex_style: co::WS_EX::LEFT | co::WS_EX::CLIENTEDGE,
285			ctrl_id: 0,
286			resize_behavior: (Horz::None, Vert::None),
287		}
288	}
289}